/**
 * @brief LCD driver implementation
 * @author Gilad Avidov
 */
#include <stdio.h>
#include "common.h"
#include "lcd.h"
#include "sched.h"

xdata at (0x8000) unsigned char LCD_CMD_WR;
xdata at (0x8001) unsigned char LCD_CMD_RD;
xdata at (0x8002) unsigned char LCD_DATA_WR;
xdata at (0x8003) unsigned char LCD_DATA_RD;

typedef enum {
    LCD_CLR_SCR_AND_CUR_HOME    = 0x1,
    LCD_ENTRY_MODE_SET_COMMAND  = 0x6,
    LCD_DISPLAY_OFF             = 0x8,
    LCD_DISPLAY_ON              = 0xC,
    LCD_UNLOCK_COMMAND          = 0x30,
    LCD_FUNCTION_SET_COMMAND    = 0x38,
} lcd_command;

#define LCD_SET_DDRAM(addr)     (0x80 | ((addr) & 0x7F))
#define LCD_SET_CGRAM(addr)     (0x40 | ((addr) & 0x3F))
#define LCD_BUSY_MASK           BIT(7)
const static unsigned char first_x_of_row[] = {0, 0x40, 0x10, 0x50};

typedef struct {
    unsigned char x;
    unsigned char y;
} LCD;

LCD lcd;


void lcdgotoaddr(unsigned char addr)
{
	lcdbusywait();
    LCD_CMD_WR = LCD_SET_DDRAM(addr);
   //printf("lcdgotoaddr(%d:0x%X)\n\r", addr, addr);
}

void lcdgotoxy(unsigned char row, unsigned char column)
{
    //printf("lcdgotoxy(x:%x y:%y)\n\r",lcd.x, lcd.y);
    lcdgotoaddr(column + first_x_of_row[row & 0x3]);
    lcd.x = column;
    lcd.y = row;
}

/*
 * @brief keep track of next char with wrapping of last char + 1 to frist
 * @return true when move to new row
 */
static void lcd_step()
{
    if (++lcd.x > LCD_MAX_X) {
        if ((++lcd.y) > LCD_MAX_Y)
            lcd.y = 0;
        lcd.x = 0;
    }
}

void lcdputch(char cc)
{
    lcdbusywait();
    //lcd_set_data();
    LCD_DATA_WR = cc;
    //printf("write char:%d:%c at[x:0x%x y:0x%x]\n\r", cc,cc, lcd.x, lcd.y);
    lcd_step();
    if (!lcd.x) /* start a new row */
        lcdgotoxy(lcd.y, lcd.x);
}

void lcdputstr(char *ss)
{
    for ( ; *ss ; ++ss)
        lcdputch(*ss);
}

/* @post register command is selected and mode of read */
void lcdbusywait()
{
    unsigned char cnt=0;
    while (LCD_CMD_RD & LCD_BUSY_MASK) ++cnt;
    //if (cnt > 3)
        //printf("lcd buzy polling cnt:%d\n\r", cnt);
}

void lcdinit()
{
    usleep(20);
    LCD_CMD_WR = LCD_UNLOCK_COMMAND;
    usleep(6);
    LCD_CMD_WR = LCD_UNLOCK_COMMAND;
    usleep(1);
    LCD_CMD_WR = LCD_UNLOCK_COMMAND;
    lcdbusywait();
    LCD_CMD_WR = LCD_FUNCTION_SET_COMMAND;
    lcdbusywait();
    LCD_CMD_WR = LCD_DISPLAY_OFF;
    lcdbusywait();
    LCD_CMD_WR = LCD_DISPLAY_ON;
    lcdbusywait();
    LCD_CMD_WR = LCD_ENTRY_MODE_SET_COMMAND;
    lcdbusywait();
    LCD_CMD_WR = LCD_CLR_SCR_AND_CUR_HOME;
}

void lcd_read_screen(xdata char *buf, int buf_len)
{
    xdata char *lcd_content_ptr = buf;
    unsigned char x, y;
    puts("dump LCD:\r");
    for (y=0; y<LCD_MAX_Y ; ++y) {
        lcdgotoxy(y,0);
        for (x=0 ; x<LCD_MAX_X ; ++x) {
            *lcd_content_ptr = LCD_DATA_RD;
            if (!(--buf_len))
                goto end_of_buf;
            printf("%c ", *lcd_content_ptr);
            ++lcd_content_ptr;
        }

        puts("\r");
    }
end_of_buf:
    *lcd_content_ptr = 0;
    printf("lcd_content_ptr:%s\n\r", buf);
}

void lcdcreatechar(unsigned char ccode, unsigned char row_vals[])
{
    unsigned char row;

    lcdbusywait();
    LCD_CMD_WR = (0x40 | ((ccode << 3) & 0x3F)) ; //LCD_SET_CGRAM(code);
    for (row=0 ; row<LCD_CHAR_ROWS ; ++row) {
        lcdbusywait();
        LCD_DATA_WR = row_vals[row];
        //printf("code:%d row:%d:0x%x\n\r", ccode, row, row_vals[row]);
    }
}

void lcdcreate_all_chars(unsigned char all_row_vals[LCD_CHAR_CUSTOM_CNT][LCD_CHAR_ROWS])
{
    unsigned char ccode;

   for (ccode = 0 ; ccode < LCD_CHAR_CUSTOM_CNT; ++ccode)
        lcdcreatechar(ccode, all_row_vals[ccode]);
}
